package renderer;

import auxiliary_classes.Vector4;
import auxiliary_classes.Matrix;

public class Camera {
	private Matrix rotationMatrix;
	private Matrix translationMatrix;
	private Matrix transformMatrix;
	
	public Camera(Vector4 los, Vector4 loc){
		this.initRotationMatrix(loc, los);
		this.initTranslationMatrix(loc);
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void initRotationMatrix(Vector4 loc, Vector4 los){
		los.inPlaceSubtract(loc);
		Vector4 z = los.normal();
		Vector4 y = new Vector4(1.0, 0.0, 0.0);
		y.inPlaceSubtract(z.scale(y.dot(z)));
		Vector4 x = z.cross(y);
		this.rotationMatrix = new Matrix(4, 4);
		rotationMatrix.setRow(0, x);
		rotationMatrix.setRow(1, y);
		rotationMatrix.setRow(2, z);
		rotationMatrix.setCell(3, 3, 1.0);
	}
	
	public void initTranslationMatrix(Vector4 loc){
		translationMatrix = Matrix.identity(4);
		translationMatrix.setColumn(3, loc);
	}
	
	public void rotateVertical(double angle){
		angle = -angle;
		double cos = Math.cos(angle);
		double sin = Math.sin(angle);
		double[][] vals = {{1, 0, 0, 0},
		                   {0, cos, -sin, 0},
		                   {0, sin, cos, 0},
		                   {0, 0, 0, 1}};
		Matrix tempRot = new Matrix(4, 4);
		tempRot.setEntireMatrix(vals);
		this.rotationMatrix = tempRot.multiply(this.rotationMatrix);
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void rotateHorizontal(double angle){
		angle = -angle;
		double cos = Math.cos(angle);
		double sin = Math.sin(angle);
		double[][] vals = {{cos, 0, sin, 0},
		                   {0, 1, 0, 0},
		                   {-sin, 0, cos, 0},
		                   {0, 0, 0, 1}};
		Matrix tempRot = new Matrix(4, 4);
		tempRot.setEntireMatrix(vals);
		this.rotationMatrix = tempRot.multiply(this.rotationMatrix);
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void roll(double angle){
		angle = -angle;
		double cos = Math.cos(angle);
		double sin = Math.sin(angle);
		double[][] vals = {{cos, -sin, 0, 0},
		                   {sin, cos, 0, 0},
		                   {0, 0, 1, 0},
		                   {0, 0, 0, 1}};
		Matrix tempRot = new Matrix(4, 4);
		tempRot.setEntireMatrix(vals);
		this.rotationMatrix = tempRot.multiply(this.rotationMatrix);
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void moveForward(double distance){
		Vector4 direction = this.transformMatrix.getRowAsVector(2);
		Vector4 location = this.translationMatrix.getColumnAsVector(3);
		direction.scale(distance);
		this.translationMatrix.setColumn(3, (Vector4) direction.add(location));
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void moveLeft(double distance){
		Vector4 direction = this.transformMatrix.getRowAsVector(0);
		Vector4 location = this.translationMatrix.getColumnAsVector(3);
		direction.scale(distance);
		this.translationMatrix.setColumn(3, (Vector4) direction.add(location));
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	public void moveUp(double distance){
		Vector4 direction = this.transformMatrix.getRowAsVector(1);
		Vector4 location = this.translationMatrix.getColumnAsVector(3);
		direction.scale(distance);
		this.translationMatrix.setColumn(3, (Vector4) direction.add(location));
		this.transformMatrix = this.rotationMatrix.multiply(this.translationMatrix);
	}
	
	//Converts a 3D vector to a 2D point for displaying, returns null if the point is behind the camera
	public double[] convertPoint(Vector4 point){
		System.out.println(this.transformMatrix.multiply(point).toString());
		Vector4 normalizedPoint = (Vector4) this.transformMatrix.multiply(point);
		double[] vals = new double[3];
		double x = normalizedPoint.getX();
		double y = normalizedPoint.getY();
		double z = normalizedPoint.getZ();
		vals[2] = Math.sqrt(x*x + y*y + z*z);
		if (z > 0){
			vals[0] = x/z;
			vals[1] = y/z;
			return vals;
		} else {
			return null;
		}
	}
	
	public static double[] adjustPoint(double[] cartesianPoint, int windowWidth, int windowHeight) {
		double[] adjusted = new double[3];
		adjusted[0] = cartesianPoint[0] + (windowWidth / 2);
		adjusted[1] = -cartesianPoint[1] + (windowHeight / 2);
		adjusted[2] = cartesianPoint[2];
		return adjusted;
	}
}
